package org.unidata.mdm.rest.v1.meta.service.enumeration;

import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.unidata.mdm.core.context.ModelChangeContext;
import org.unidata.mdm.core.type.model.EnumerationElement;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.meta.context.GetEnumerationsContext;
import org.unidata.mdm.meta.context.UpsertEnumerationsContext;
import org.unidata.mdm.meta.type.model.enumeration.EnumerationsModel;
import org.unidata.mdm.rest.v1.meta.converter.EnumerationConverter;
import org.unidata.mdm.rest.v1.meta.converter.GenericModelInfoConverter;
import org.unidata.mdm.rest.v1.meta.exception.MetaRestExceptionIds;
import org.unidata.mdm.rest.v1.meta.ro.GetGenericModelInfoResultRO;
import org.unidata.mdm.rest.v1.meta.ro.UpsertGenericModelInfoRequestRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.DeleteEnumerationResultRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.EnumerationDefinitionRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.GetEnumerationResultRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.GetEnumerationsResultRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.ImportEnumerationResultRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.UpsertEnumerationRequestRO;
import org.unidata.mdm.rest.v1.meta.ro.enumerations.UpsertEnumerationResultRO;
import org.unidata.mdm.rest.v1.meta.service.AbstractMetaModelRestService;
import org.unidata.mdm.system.exception.PlatformBusinessException;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

/**
 * Enumeration rest controller
 *
 * @author Alexandr Serov
 * @since 23.11.2020
 **/
@Path(EnumerationRestService.SERVICE_PATH)
@Consumes({"application/json"})
@Produces({"application/json"})
public class EnumerationRestService extends AbstractMetaModelRestService {

    public static final String SERVICE_PATH = "enumerations";

    public static final String SERVICE_TAG = "enumerations";

    private static final String ENUM_NOT_FOUND_ERROR = "Enumeration %s not found";

    /**
     * Gets all enumerations.
     *
     * @return response
     */
    @GET
    @Operation(
        description = "Enumerations list.",
        method = HttpMethod.GET,
        tags = SERVICE_TAG)
    public GetEnumerationsResultRO getAll() {
        Collection<EnumerationElement> enums = ObjectUtils.defaultIfNull(instance(Descriptors.ENUMERATIONS).getEnumerations(), Collections.emptyList());
        GetEnumerationsResultRO result = new GetEnumerationsResultRO();
        result.setEnumerations(enums.stream()
            .map(EnumerationConverter::to)
            .collect(Collectors.toList())
        );
        return result;
    }

    @GET
    @Path("{enumerationName}")
    @Operation(
        description = "Get an existing source system by name",
        method = HttpMethod.GET,
        tags = SERVICE_TAG)
    public GetEnumerationResultRO getByName(
        @Parameter(description = "Enumeration name.", in = ParameterIn.PATH)
        @PathParam("enumerationName") String enumerationName) {
        GetEnumerationResultRO result = new GetEnumerationResultRO();
        result.setEnumeration(EnumerationConverter.to(enumerationElementByName(enumerationName)));
        return result;
    }


    @GET
    @Path("/export")
    @Produces(MediaType.TEXT_XML)
    @Operation(
        description = "Export enumerations model.",
        method = HttpMethod.GET,
        parameters = {@Parameter(description = "Storage ID. Optional", in = ParameterIn.QUERY, name = "storageId")},
        responses = {
            @ApiResponse(content = @Content(schema = @Schema(implementation = StreamingOutput.class)), responseCode = "200")
        }, tags = SERVICE_TAG)
    public Response exportEnumerations(@QueryParam("storageId") String storageId) {
        return exportXmlFile(SERVICE_TAG, instance(Descriptors.ENUMERATIONS, storageId).toSource());
    }

    @POST
    @Path("/import")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(
        description = "Import enumerations model. Only xml is supported.",
        method = HttpMethod.POST,
        requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA), description = "Source file."),
        tags = SERVICE_TAG)
    public ImportEnumerationResultRO importEnumerations(@Multipart(value = "file") Attachment attachment) {
        return importXmlFile(attachment, EnumerationsModel.class, enumerationsModel -> {
                ImportEnumerationResultRO result = new ImportEnumerationResultRO();
                result.setInstanceId(enumerationsModel.getInstanceId());
                result.setStorageId(enumerationsModel.getStorageId());
                metaModelService.upsert(UpsertEnumerationsContext.builder()
                    .upsertType(ModelChangeContext.ModelChangeType.FULL)
                    .update(enumerationsModel.getValues())
                    .storageId(enumerationsModel.getStorageId())
                    .instanceId(enumerationsModel.getInstanceId())
                    .name(enumerationsModel.getName())
                    .waitForFinish(true)
                    .build());
                return result;
            }
        );
    }

    @POST
    @Operation(
        description = "Upsert enumeration",
        method = HttpMethod.POST,
        requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = UpsertEnumerationRequestRO.class)),
            description = "Upsert request."),
        tags = SERVICE_TAG)
    public UpsertEnumerationResultRO upsert(UpsertEnumerationRequestRO req) {
        Objects.requireNonNull(req, "Request can't be null");
        EnumerationDefinitionRO enumeration = notNull("enumeration", req.getEnumeration());
        UpsertEnumerationResultRO result = new UpsertEnumerationResultRO();
        metaModelService.upsert(UpsertEnumerationsContext.builder()
            .update(EnumerationConverter.from(enumeration))
            .build());
        result.setName(enumeration.getName());
//        result.setEnumeration(EnumerationConverter.to(findEnumerationElementByName(enumeration.getName())));
        return result;
    }

    @POST
    @Path("/info")
    @Operation(
        description = "Upsert generic enumeration model info.",
        method = HttpMethod.POST,
        requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = UpsertGenericModelInfoRequestRO.class)),
            description = "Upsert request."),
        tags = SERVICE_TAG)
    public GetGenericModelInfoResultRO upsert(UpsertGenericModelInfoRequestRO req) {

        metaModelService.upsert(UpsertEnumerationsContext.builder()
                .name(req.getName())
                .displayName(req.getDisplayName())
                .description(req.getDescription())
                .waitForFinish(true)
                .build());

        return GenericModelInfoConverter.to(metaModelService.get(GetEnumerationsContext.builder()
                .modelInfo(true)
                .build()));
    }

    @GET
    @Path("/info")
    @Operation(
        description = "Gets generic model info.",
        method = HttpMethod.GET,
        tags = SERVICE_TAG)
    public GetGenericModelInfoResultRO get() {
        return GenericModelInfoConverter.to(metaModelService.get(GetEnumerationsContext.builder()
                .modelInfo(true)
                .build()));
    }

    @DELETE
    @Path("{enumerationName}")
    @Operation(
        description = "Removes an existing source system.",
        method = HttpMethod.DELETE,
        tags = SERVICE_TAG)
    public DeleteEnumerationResultRO delete(
        @Parameter(description = "Enumeration id.", in = ParameterIn.PATH)
        @PathParam(value = "enumerationName") String enumerationName) {
        EnumerationElement enumValue = enumerationElementByName(enumerationName);
        DeleteEnumerationResultRO result = new DeleteEnumerationResultRO();
        metaModelService.upsert(UpsertEnumerationsContext.builder()
            .delete(enumerationName)
            .build());
        result.setId(enumerationName);
        return result;
    }

    private EnumerationElement findEnumerationElementByName(String enumName) {
        return instance(Descriptors.ENUMERATIONS).getEnumeration(notNull("enumeration name", enumName));
    }

    private EnumerationElement enumerationElementByName(String enumName) {
        EnumerationElement result = findEnumerationElementByName(enumName);
        if (result == null) {
            throw new PlatformBusinessException(String.format(ENUM_NOT_FOUND_ERROR, enumName), MetaRestExceptionIds.EX_META_ENUMERATION_NOT_FOUND);
        }
        return result;
    }

}
